Repository navigation
Conversation
Electric v2Workspaces rows carry ISO-string timestamps at runtime (the shape parser doesn't handle timestamptz), despite the SelectV2Workspace type saying Date. The new sidebar sort was the first code to call .getTime() on workspace.updatedAt, so cloud-fallback rows (unreachable host with no snapshot) crashed the whole dashboard render. Convert cloud rows to real Dates where they enter mergeHostWorkspaces so the type holds for every consumer, and make sortDashboardSidebarProjects coerce timestamps defensively — garbage data now falls through to the name/id tiebreakers instead of throwing mid-render. Claude-Session: https://claude.ai/code/session_01SSUQYFamkYAjs5sDQC5RLS
…sort
"Last updated" / "Date created" previously reordered only the project
groups; each project's children kept their manual tabOrder. Now workspaces
sort by the mode inside each project, workspaces inside sections sort too,
and sections rank among loose workspaces by their most recent activity
("updated") or their own createdAt ("created"). The local main workspace
stays pinned first, matching its treatment elsewhere in the sidebar.
Claude-Session: https://claude.ai/code/session_01SSUQYFamkYAjs5sDQC5RLS
v2_workspaces.updatedAt only moves on metadata writes, so sending an agent a message never reordered the sidebar. Each host's terminal-agent bindings record lastEventAt per workspace; the sidebar now polls terminalAgents.list per reachable host (only while the sort mode is 'updated'), threads the newest event time onto each workspace row, and sorts workspaces, sections, and projects by max(updatedAt, agent activity). Claude-Session: https://claude.ai/code/session_01SSUQYFamkYAjs5sDQC5RLS
📝 WalkthroughWalkthroughThe sidebar adds persisted manual, created, and updated sorting, project filtering, activity-aware ordering, timestamp normalization, and drag restrictions during filtered views. Automation failure indicators now use destructive theme tokens. ChangesDashboard sidebar sorting and activity
Automation failure status styling
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Sidebar
participant SidebarData
participant HostTerminalAgents
participant ProjectBuilder
Sidebar->>SidebarData: select updated sorting
SidebarData->>HostTerminalAgents: poll agent activity
HostTerminalAgents-->>SidebarData: return latest lastEventAt
SidebarData->>ProjectBuilder: pass workspace activity map
ProjectBuilder-->>Sidebar: render sorted projects
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Ready to review this PR? Stage has broken it down into 9 individual chapters for you: Chapters generated by Stage for commit be67fa8 on Jul 27, 2026 11:26pm UTC. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md (1)
44-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a language to the fenced code block (markdownlint MD040).
Static analysis flags this fence for missing a language specifier.
📝 Proposed fix
-``` +```text max(updatedAt, newest lastEventAt across the workspace's agent bindings)</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.mdaround lines 44 - 46, Add the
text language specifier to the fenced code block containing the max(updatedAt,
newest lastEventAt across the workspace's agent bindings) expression, preserving
its content unchanged.</details> <!-- cr-comment:v1:ef6f166a081adf59a846c384 --> _Source: Linters/SAST tools_ </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.Inline comments:
In
@apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts:
- Around line 205-215: The setSidebarProjectSortMode callback currently inserts
DEFAULT_V2_USER_PREFERENCES when the live query has not hydrated. Destructure
and use isReady from useLiveQuery, allowing the insert only when readiness is
confirmed; otherwise defer the write while preserving the existing update path
for an available preference row.In
@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx:
- Line 294: Update the nested drag-and-drop configuration at isInnerDragDisabled
in DashboardSidebar so nested workspace/section dragging is disabled whenever
filters are active or the current sorting mode is not manual. Reuse the existing
isDragDisabled state for this prop to keep behavior consistent with the outer
drag controls.In
@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/sortDashboardSidebarProjects.ts:
- Around line 32-43: Update getProjectActivityTimestamp to filter out NaN values
from workspaces.map(getWorkspaceActivityTime) before applying Math.max, matching
the existing getChildTimestamp section-level behavior. Preserve the fallback
handling for projects without workspaces and ensure valid workspace timestamps
still determine the project activity when another workspace is unparseable.
Nitpick comments:
In@apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md:
- Around line 44-46: Add the text language specifier to the fenced code block
containing the max(updatedAt, newest lastEventAt across the workspace's agent
bindings) expression, preserving its content unchanged.</details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `047edc39-4001-4ba5-bf59-b21cd22b2e5c` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 0f4656620f34e1d508f9da02ef16bf2714c3ba99 and be67fa804f87166caee96e936552a7d9d3c61b2e. </details> <details> <summary>📒 Files selected for processing (27)</summary> * `apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md` * `apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.utils.test.ts` * `apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.utils.ts` * `apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/$automationId/components/PreviousRunsList/PreviousRunsList.tsx` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/components/AutomationRow/AutomationRow.tsx` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsx` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarExpandedProjectContent/DashboardSidebarExpandedProjectContent.tsx` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/components/DashboardSidebarProjectsFilterInput/DashboardSidebarProjectsFilterInput.tsx` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/components/DashboardSidebarProjectsFilterInput/index.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/components/DashboardSidebarProjectsSortMenu/DashboardSidebarProjectsSortMenu.tsx` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/components/DashboardSidebarProjectsSortMenu/index.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.test.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useSidebarDnd/useSidebarDnd.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/filterDashboardSidebarProjects/filterDashboardSidebarProjects.test.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/filterDashboardSidebarProjects/filterDashboardSidebarProjects.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/filterDashboardSidebarProjects/index.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/index.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/sortDashboardSidebarProjects.test.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/sortDashboardSidebarProjects.ts` * `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/testProjectFixtures.ts` * `apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| const setSidebarProjectSortMode = useCallback( | ||
| (next: SidebarProjectSortMode) => { | ||
| const existing = collections.v2UserPreferences.get( | ||
| V2_USER_PREFERENCES_ID, | ||
| ); | ||
| if (!existing) { | ||
| collections.v2UserPreferences.insert({ | ||
| ...DEFAULT_V2_USER_PREFERENCES, | ||
| sidebarProjectSortMode: next, | ||
| }); | ||
| return; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C4 'useLiveQuery\(' apps/desktop/src/renderer
rg -n -C5 '\b(isReady|isLoading)\b' apps/desktop/src/renderer
rg -n -C5 'setSidebarProjectSortMode|v2UserPreferences\.(insert|update)' apps/desktop/src/rendererRepository: superset-sh/superset
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target hook outline/size =="
wc -l apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts
ast-grep outline apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts || true
echo "== target file relevant sections =="
sed -n '1,260p' apps/desktop/src/renderer/hooks使用V2UserPreferences/useV2UserPreferences.ts | cat -n 2>/dev/null || true
echo "== all occurrences of useV2UserPreferences =="
rg -n -C3 'useV2UserPreferences|useV2Project' apps/desktop/src/renderer | head -n 200Repository: superset-sh/superset
Length of output: 29475
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target file relevant sections =="
sed -n '1,238p' apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts | cat -n
echo "== package versions =="
node - <<'JS'
const fs = require('fs');
for (const f of ['package.json','apps/desktop/package.json']) {
if (!fs.existsSync(f)) continue;
const p = JSON.parse(fs.readFileSync(f,'utf8'));
console.log(f, {
tanstackReactDb: (p.dependencies?.['`@tanstack/react-db`'] || p.devDependencies?.['`@tanstack/react-db`']) &&
p.dependencies?.['`@tanstack/react-db`'] || p.devDependencies?.['`@tanstack/react-db`']
});
}
JS
echo "== nearby hook live query consumers (narrow) =="
rg -n -C2 'setSidebarProjectSortMode|useV2UserPreferences\s*\(\)' \
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components \
apps/desktop/src/renderer/routes/_authenticated/_dashboard \
apps/desktop/src/renderer/screens \
| head -n 220Repository: superset-sh/superset
Length of output: 21665
🌐 Web query:
@tanstack/react-db useLiveQuery isReady isLoading hydration cache data
💡 Result:
In the context of the TanStack ecosystem, @tanstack/react-db and @tanstack/react-query serve different primary purposes, though TanStack DB is built on top of TanStack Query [1]. useLiveQuery is a hook provided by @tanstack/react-db for managing reactive, database-like collections [2][3][1]. It automatically re-runs when the underlying data in a collection changes, eliminating the need for manual cache invalidation [1]. Key concepts regarding your query include: Loading States (isLoading) In useLiveQuery, the isLoading property is a boolean that indicates whether the query is currently performing its initial fetch [3]. Like TanStack Query's useQuery, this state is typically used to render loading indicators [2][3]. Hydration and Cache Because TanStack DB uses TanStack Query's caching and synchronization mechanisms under the hood, it interacts with the same underlying infrastructure [1]. 1. Hydration: This is the process of transferring prefetched state from the server to the client [4][5]. It uses the same hydrate and dehydrate utilities as TanStack Query to restore state into the QueryClient cache [4][5]. 2. Cache: Data fetched by useLiveQuery is stored in the TanStack Query cache [1]. When hydrating, TanStack Query intelligently merges this state with any existing data in the cache based on timestamps [4][5]. isReady There is no standard isReady property in the primary TanStack Query or TanStack DB hook return objects. If you are seeing this, it may be a custom implementation, a legacy property, or a misidentification of states like isPending or isSuccess [6]. The core statuses used by the TanStack ecosystem to determine if data is available are isPending, isError, and isSuccess [6]. Compatibility Note If you are migrating from @tanstack/react-query to @tanstack/react-db, note that while they share concepts, the APIs differ [7]. useLiveQuery replaces the standard useQuery pattern for collection management [7]. When dealing with SSR and hydration, ensure you are prefetching data on the server and using the HydrationBoundary or hydrate utilities on the client to avoid state mismatches [8][9][4]. Top Results: [2], [3], [1], [4], [5]
Citations:
- 1: https://blog.logrocket.com/tanstack-db-ux/
- 2: https://tanstack.com/db/latest/docs/framework/react/reference/functions/useLiveQuery
- 3: https://tanstack.com/db/latest/docs/framework/react/overview
- 4: https://tanstack.com/query/latest/docs/framework/react/reference/hydration.md
- 5: https://tanstack-query.mintlify.app/api/hydration
- 6: https://github.com/TanStack/query/blob/main/docs/framework/react/guides/queries.md
- 7: https://gist.github.com/alavkx/b93808587ebc62cc2fe7b1eb2bdce6f6
- 8: https://tanstack.com/query/latest/docs/framework/react/guides/ssr
- 9: https://tanstack.com/query/v5/docs/framework/react/guides/ssr
🌐 Web query:
docs TanStack DB useLiveQuery API return { data, isReady isLoading }
💡 Result:
Yes, the useLiveQuery hook in TanStack DB returns an object that includes the properties { data, isReady, isLoading }, among others [1][2][3]. Specifically, the hook returns an object with the following shape [1][2]: - data: The result of the query. Its type is InferResultType (or an array, depending on the query) and it can be undefined if the query is not yet ready or is disabled [4][1][5]. - isLoading: A boolean indicating if the query is in a loading state [1][2][3]. - isReady: A boolean indicating if the query has finished its initial load and is ready to display [1][2]. In addition to these, the returned object typically includes several other useful properties for managing query state [1][2]: - status: A string representing the current status of the query (e.g., 'loading', 'error', 'success', 'disabled') [1][2][3]. - isError: A boolean indicating if the query encountered an error [1][2]. - isIdle: A boolean indicating if the query is idle [1][2]. - isCleanedUp: A boolean indicating if the query has been cleaned up [1][2]. - collection: The collection object associated with the query [1][2]. - state: A Map containing the reactive state of the query [1][2]. You can destructure these properties directly from the result of the hook [2][3]. Returning undefined or null from the useLiveQuery callback function will disable the query, in which case the status will be 'disabled' and data will be undefined [2][3].
Citations:
- 1: https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts
- 2: https://github.com/TanStack/db/blob/main/packages/react-db/skills/react-db/SKILL.md
- 3: https://tanstack.com/db/latest/docs/guides/live-queries.md
- 4: https://tanstack.com/db/latest/docs/framework/react/reference/functions/useLiveQuery
- 5: https://tanstack.com/db/latest/docs/framework/svelte/reference/functions/useLiveQuery
Wait for strict live-query readiness before writing user preferences.
useLiveQuery is only destructured for data, so isReady is ignored when collections.v2UserPreferences.get(...) returns no row while hydration is in progress. In that state, setSidebarProjectSortMode can insert a default preference before the persisted row is available. Gate the insert on isReady (and otherwise defer the write) so a concurrent server/provided row cannot collide or be lost.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts`
around lines 205 - 215, The setSidebarProjectSortMode callback currently inserts
DEFAULT_V2_USER_PREFERENCES when the live query has not hydrated. Destructure
and use isReady from useLiveQuery, allowing the insert only when readiness is
confirmed; otherwise defer the write while preserving the existing update path
for an available preference row.
Source: Coding guidelines
| isCollapsed={isCollapsed} | ||
| isDraggingProject={activeProject != null} | ||
| isDragDisabled={isDragDisabled} | ||
| isInnerDragDisabled={isFilterActive} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Disable nested DnD for non-manual sorting too.
Line 294 only disables workspace/section dragging while filtering. In created and updated modes, child rows are sorted independently, so a drop persists a manual order that immediately appears to revert and resurfaces when switching back to Manual. Pass isDragDisabled here.
As per PR objectives, nested workspace dragging must be disabled in filtered or non-manual views.
Proposed fix
- isInnerDragDisabled={isFilterActive}
+ isInnerDragDisabled={isDragDisabled}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| isInnerDragDisabled={isFilterActive} | |
| isInnerDragDisabled={isDragDisabled} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx`
at line 294, Update the nested drag-and-drop configuration at
isInnerDragDisabled in DashboardSidebar so nested workspace/section dragging is
disabled whenever filters are active or the current sorting mode is not manual.
Reuse the existing isDragDisabled state for this prop to keep behavior
consistent with the outer drag controls.
| // The host only bumps a project's own updatedAt on metadata patches (e.g. | ||
| // rename), so "recent activity" comes from the workspaces inside it. | ||
| export function getProjectActivityTimestamp( | ||
| project: DashboardSidebarProject, | ||
| ): number { | ||
| const workspaces = getProjectChildrenWorkspaces(project.children); | ||
| if (workspaces.length === 0) { | ||
| const updatedAt = toTime(project.updatedAt); | ||
| return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt; | ||
| } | ||
| return Math.max(...workspaces.map(getWorkspaceActivityTime)); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
getProjectActivityTimestamp isn't NaN-safe like its section-level counterpart.
Math.max(...workspaces.map(getWorkspaceActivityTime)) propagates NaN if any single workspace has an unparseable updatedAt and no lastAgentActivityAt. That poisons the entire project's activity timestamp, discarding every other (possibly very recent) workspace's activity and falling back to name-order sorting. getChildTimestamp's section branch (Lines 80-85) already filters out NaN before taking Math.max for exactly this reason — this function should do the same.
🐛 Proposed fix mirroring the section-level NaN filter
export function getProjectActivityTimestamp(
project: DashboardSidebarProject,
): number {
const workspaces = getProjectChildrenWorkspaces(project.children);
- if (workspaces.length === 0) {
- const updatedAt = toTime(project.updatedAt);
- return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt;
- }
- return Math.max(...workspaces.map(getWorkspaceActivityTime));
+ const activity = workspaces
+ .map(getWorkspaceActivityTime)
+ .filter((time) => !Number.isNaN(time));
+ if (activity.length === 0) {
+ const updatedAt = toTime(project.updatedAt);
+ return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt;
+ }
+ return Math.max(...activity);
}A regression test with one valid-timestamp workspace and one NaN-timestamp workspace in the same project would help lock this in.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // The host only bumps a project's own updatedAt on metadata patches (e.g. | |
| // rename), so "recent activity" comes from the workspaces inside it. | |
| export function getProjectActivityTimestamp( | |
| project: DashboardSidebarProject, | |
| ): number { | |
| const workspaces = getProjectChildrenWorkspaces(project.children); | |
| if (workspaces.length === 0) { | |
| const updatedAt = toTime(project.updatedAt); | |
| return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt; | |
| } | |
| return Math.max(...workspaces.map(getWorkspaceActivityTime)); | |
| } | |
| // The host only bumps a project's own updatedAt on metadata patches (e.g. | |
| // rename), so "recent activity" comes from the workspaces inside it. | |
| export function getProjectActivityTimestamp( | |
| project: DashboardSidebarProject, | |
| ): number { | |
| const workspaces = getProjectChildrenWorkspaces(project.children); | |
| const activity = workspaces | |
| .map(getWorkspaceActivityTime) | |
| .filter((time) => !Number.isNaN(time)); | |
| if (activity.length === 0) { | |
| const updatedAt = toTime(project.updatedAt); | |
| return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt; | |
| } | |
| return Math.max(...activity); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/sortDashboardSidebarProjects.ts`
around lines 32 - 43, Update getProjectActivityTimestamp to filter out NaN
values from workspaces.map(getWorkspaceActivityTime) before applying Math.max,
matching the existing getChildTimestamp section-level behavior. Preserve the
fallback handling for projects without workspaces and ensure valid workspace
timestamps still determine the project activity when another workspace is
unparseable.
Summary
Re-lands the dashboard sidebar filter + sort feature (#5956, reverted in #5996) with the fixes that motivated the revert:
timestamptz, despite theSelectV2Workspacetype). They're normalized to realDates where they entermergeHostWorkspaces, and the sort coerces defensively (toTime, NaN-safe tie-breaks) so bad data can never crash the sidebar again. Fixes theworkspace.updatedAt.getTime is not a functioncrash.v2_workspaces.updatedAtonly moves on metadata writes, so messaging an agent never reordered anything. The sidebar now polls each reachable host'sterminalAgents.list(10s interval, only while the sort mode is "Last updated") and ranks everything bymax(updatedAt, newest agent hook event). Sending a Claude message reorders within ~10s.apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.mdrecords the signal semantics, the polling trade-off, and the follow-up paths (event-bus push for instant reordering; cloud-persisted activity as the long-term model; Electric timestamp-parser root fix as its own PR).Verification
updatedAt-only ordering; a syntheticUserPromptSubmiton a 3-day-idle workspace moved it to the top of its project in ~4s.Notes
bg-destructivecolor changes that style(desktop): soften automation failed red to destructive token #6001 also contains. Merge either first; the other becomes a trivial rebase/close.updatedAtranking.https://claude.ai/code/session_01SSUQYFamkYAjs5sDQC5RLS
Summary by cubic
Re-lands the dashboard sidebar filter and sort, and makes “Last updated” use real agent activity so recent conversations reorder projects and workspaces. Also fixes a crash from cloud string timestamps.
New Features
sidebarProjectSortMode.updatedAt, latest agent event), polling each reachable host’sterminalAgents.listevery 10s only while this mode is active. Applies at workspace, section, and project levels; the local main workspace stays first.Bug Fixes
createdAt/updatedAtstrings toDateinmergeHostWorkspaces; sorting uses NaN-safe coercion with name/id tie-breakers.bg-destructive.Written for commit be67fa8. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes